using System;
using System.Collections.Generic;
using System.Linq;
using Think.Helpers;

namespace Think.Problems.Solitaire
{
    public class SolitaireState
    {
        private Deck _stock = null;
        private List<string> _waste = new List<string>();

        private readonly Foundation[] _foundations; 

        private readonly Tableau[] _tableaus;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="a_seed">Game seed.</param>
        public SolitaireState(int a_seed)
            : this(a_seed, 7)
        {

        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="a_seed">Game seed.</param>
        /// <param name="a_tableauCount">Number of tableaus.</param>
        public SolitaireState(int a_seed, int a_tableauCount)
        {
            _stock = new Deck(a_seed);

            _foundations = new[]
            {
                new Foundation(CardSuit.Spade),
                new Foundation(CardSuit.Heart),
                new Foundation(CardSuit.Diamond),
                new Foundation(CardSuit.Club),
            };

            var rule = new SolitaireRule();
            _tableaus = new Tableau[a_tableauCount];

            for (var i = 0; i < a_tableauCount; i++)
            {
                for (var j = 0; j < a_tableauCount; j++)
                {
                    if (i == 0)
                        _tableaus[j] = new Tableau(new SolitaireRule());

                    _tableaus[j].Add(_stock, j + 1);
                }
            }

            _waste.PushFirst(_stock.Draw());
        }
        
        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_other">Other state.</param>
        protected SolitaireState(SolitaireState a_other)
        {
            _stock = a_other._stock.Clone();

            _waste = new List<string>(a_other._waste);

            _foundations = a_other._foundations.Select(i => i.Clone()).ToArray();

            _tableaus = a_other._tableaus.Select(i => i.Clone()).ToArray();
        }

        /// <summary>
        /// Tableaus in this state.
        /// </summary>
        public Tableau[] Tableaus
        {
            get { return _tableaus.ToArray(); }
        }

        /// <summary>
        /// Stock in this state.
        /// </summary>
        public String StockTop
        {
            get { return _waste.FirstOrDefault(); }
        }

        /// <summary>
        /// Foundations. 
        /// </summary>
        public Foundation[] Foundations
        {
            get { return _foundations; }
        }

        /// <summary>
        /// Clone this Solitaire State
        /// </summary>
        /// <returns>Clone of this state.</returns>
        public SolitaireState Clone()
        {
            return new SolitaireState(this);
        }

        /// <summary>
        /// Perform the given action (<paramref name="a_action"/>) on this state.
        /// </summary>
        /// <param name="a_action">Action.</param>
        public void Act(Action a_action)
        {
            if (a_action is DrawAction)
                Draw();
            else if (a_action is RecycleAction)
                Recycle();
            else if (a_action is FlipAction)
                Flip((a_action as FlipAction).Tableau);
            else if (a_action is ReturnAction)
                Return((a_action as ReturnAction).Tableau, (a_action as ReturnAction).Foundation);
            else if (a_action is MoveAction)
                Move((a_action as MoveAction).From, (a_action as MoveAction).To, (a_action as MoveAction).Split);
        }

        /// <summary>
        /// Draw a card from the stock pile onto the waste pile.
        /// </summary>
        public void Draw()
        {
            if (_stock.Count <= 0)
                Recycle();
            else
                _waste.PushFirst(_stock.Draw());
        }

        /// <summary>
        /// Recycle the waste pile into the stock pile.
        /// </summary>
        public void Recycle()
        {
            _waste.Reverse();
            _stock = new Deck(_waste);
            _waste.Clear();

            _waste.PushFirst(_stock.Draw());
        }

        /// <summary>
        /// Flip the face down card on the tableau with the given index (<paramref name="a_tableau"/>).
        /// </summary>
        /// <param name="a_tableau">Tableau index.</param>
        public void Flip(int a_tableau)
        {
            _tableaus[a_tableau].Flip();
        }

        /// <summary>
        /// Return the top card from the tableau with the given index (<paramref name="a_tableau"/>) to the foundation
        ///     with the given index (<paramref name="a_foundation"/>).
        /// </summary>
        /// <param name="a_tableau"></param>
        /// <param name="a_foundation"></param>
        public void Return(int a_tableau, int a_foundation)
        {
            if (a_tableau < 0)
            {
                _foundations[a_foundation].Set(_waste.PopFirst());
            }
            else
            {
                var tableau = _tableaus[a_tableau];
                var index = tableau.FaceUpCount - 1;
                var travelling = tableau.Split(index);

                _foundations[a_foundation].Set(travelling.TopCard);
            }
        }

        /// <summary>
        /// Move cards from the tableau with the given index (<paramref name="a_from"/>) to the tableau with the given 
        ///     index (<paramref name="a_to"/>) using the given index (<paramref name="a_split"/>) to split the 
        ///     tableau.
        /// </summary>
        /// <param name="a_from">From tableau index.</param>
        /// <param name="a_to">To tableau index.</param>
        /// <param name="a_split">Split index.</param>
        public void Move(int a_from, int a_to, int a_split)
        {
            if (a_from < 0)
            {
                var to = _tableaus[a_to];
                var travelling = new Tableau();
                travelling.Add(_waste.PopFirst(), CardSide.FaceUp);
                to.Set(travelling);
            }
            else
            {
                var from = _tableaus[a_from];
                var to = _tableaus[a_to];
                var travelling = from.Split(a_split);
                to.Set(travelling);
            }
        }

        /// <summary>
        /// Available actions.
        /// </summary>
        /// <returns>Array of actions.</returns>
        public Action[] Actions()
        {
            var actions = new List<Action>();

            if (_stock.Count > 0)
                actions.Add(new DrawAction());
            else if (_waste.Any())
                actions.Add(new RecycleAction());

            if (_waste.Any())
            {
                var drawCard = _waste.First();

                var drawCardSuit = Card.Suit(drawCard);

                var foundation = _foundations.First(j => j.Suit == drawCardSuit);
                if (foundation.CanSet(drawCard))
                {
                    var foundationIndex = Array.IndexOf(_foundations, foundation);
                    actions.Add(new ReturnAction { Foundation = foundationIndex, Tableau = -1 });
                }

                for (var i = 0; i < _tableaus.Length; i++)
                {
                    if (_tableaus[i].CanSet(drawCard))
                        actions.Add(new MoveAction { From = -1, To = i, Split = 0, Boost = 0.75 });
                }
            }

            for (var i = 0; i < _tableaus.Length; i++)
            {
                if (_tableaus[i].CanFlip)
                    actions.Add(new FlipAction {Tableau = i});

                var topCard = _tableaus[i].TopCard;
                if (topCard != null)
                {
                    var topCardSuit = Card.Suit(topCard);

                    var foundation = _foundations.First(j => j.Suit == topCardSuit);
                    if (foundation.CanSet(topCard))
                    {
                        var foundationIndex = Array.IndexOf(_foundations, foundation);
                        actions.Add(new ReturnAction { Foundation = foundationIndex, Tableau = i });
                    }
                }

                for (var j = 0; j < _tableaus.Length; j++)
                {
                    for (var k = 0; k < _tableaus[j].FaceUp.Length; k++)
                    {
                        var card = _tableaus[j].FaceUp[k];

                        if (_tableaus[i].CanSet(card))
                        {
                            double boost = 0;
                            if (k == 0)
                            {
                                if (_tableaus[i].FaceDownCount <= 0 && 
                                    _tableaus[j].FaceDownCount <= 0 &&
                                    Card.RankValue(card) == Card.King)
                                    continue;

                                boost = 0.50;
                            }
                            actions.Add(new MoveAction {From = j, To = i, Split = k, Boost = boost});
                        }
                    }
                }
            }

            return actions.ToArray();
        }

        /// <summary>
        /// Determine whether the given object (<paramref name="a_other"/>) is equal to this object.
        /// </summary>
        /// <param name="a_other">Other object to compare with this object.</param>
        /// <returns>True if objects are equal; otherwise false.</returns>
        public override bool Equals(object a_other)
        {
            if (ReferenceEquals(this, a_other))
                return true;

            return Equals(a_other as SolitaireState);
        }

        /// <summary>
        /// Determine whether the given state (<paramref name="a_other"/>) is equal to this state.
        /// </summary>
        /// <param name="a_other">Other state to compare with this one.</param>
        /// <returns>True if states are equal; otherwise false.</returns>
        public virtual bool Equals(SolitaireState a_other)
        {
            if (a_other == null)
                return false;

            if (!_stock.Equals(a_other._stock))
                return false;

            if (!_waste.SequenceEqual(a_other._waste))
                return false;

            if (!_foundations.SequenceEqual(a_other._foundations))
                return false;

            if (!_tableaus.SequenceEqual(a_other._tableaus))
                return false;

            return true;
        }


        public override int GetHashCode()
        {
            
        }
    }

}
